﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Client.Services;
using System.ServiceModel;
using System.Web.Configuration;
using System.Configuration;

namespace Applications.Framework.Web.Providers
{
    public class CrmMembershipProvider : MembershipProvider
    {

        #region ctor

        public CrmMembershipProvider()
            : base()
        {
            InitializeCrmConnection("");
        }

        public CrmMembershipProvider(string CrmConnectionString)
            : base()
        {
            InitializeCrmConnection(CrmConnectionString);
        }

        public CrmMembershipProvider(string OrganizationServiceUrl, string UserName, string Password, string DomainName)
            : base()
        {
            InitializeCrmConnection(string.Format("Url={0}; Home Realm Uri={1}; Username={2}; Password={3}; Domain={4}",
                            OrganizationServiceUrl,
                            "",
                            UserName,
                            Password,
                            DomainName));
        }


        #endregion

        #region Private Helper Methods

        ProviderSettings Settings = null;
        private void InitializeProviderSettings()
        {
            MembershipSection section = (MembershipSection)System.Web.Configuration.WebConfigurationManager.GetSection("system.web/membership");
            Settings = section.Providers["CrmMembershipProvider"];

        }

        private Microsoft.Xrm.Client.CrmConnection Connection = null;
        private void InitializeCrmConnection(string CrmConnectionString)
        {
            try
            {
                InitializeProviderSettings();

                if (string.IsNullOrEmpty(CrmConnectionString))
                {
                    string ConName = Settings.Parameters["connectionStringName"];
                    CrmConnectionString = System.Web.Configuration.WebConfigurationManager.ConnectionStrings[ConName].ConnectionString;
                }
                Connection = Microsoft.Xrm.Client.CrmConnection.Parse(CrmConnectionString);
                Connection.ServiceConfigurationInstanceMode = ServiceConfigurationInstanceMode.PerInstance;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }



        private Entity RetrieveMemberBy(string FieldName, string Value)
        {
            try
            {
                FilterExpression filter = new FilterExpression(LogicalOperator.And);
                filter.AddCondition(new ConditionExpression(FieldName, ConditionOperator.Equal, Value));
                filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
                EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
                if (col.Entities.Count == 1)
                {
                    return col.Entities.First();
                }
                else
                {
                    return null;
                }
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }

        private MembershipUserCollection ConvertToMembershipUserCollection(EntityCollection col)
        {
            MembershipUserCollection users = new MembershipUserCollection();

            foreach (Entity ent in col.Entities)
            {
                users.Add(ConvertToMembershipUser(ent));
            }
            return users;
        }

        private DateTime GetDateOrMin(Entity entity, string FieldName)
        {
            DateTime? dt = entity.GetAttributeValue<DateTime?>(FieldName);
            if (!dt.HasValue)
            {
                return DateTime.MinValue;
            }
            else
            {
                return dt.Value;
            }
        }
        private MembershipUser ConvertToMembershipUser(Entity entity)
        {
            return new MembershipUser("CrmMembershipProvider",
                entity.GetAttributeValue<string>(UserNameField),
                entity.Id,
                entity.GetAttributeValue<string>(EmailField),
                entity.GetAttributeValue<string>(PasswordQuestionField),
                entity.GetAttributeValue<string>(PasswordAnswerField),
                entity.GetAttributeValue<bool>(IsApprovedField),
                entity.GetAttributeValue<bool>(IsLockedoutField),
                entity.GetAttributeValue<DateTime>("createdon"),
                GetDateOrMin(entity, LastLoginDateField),
                GetDateOrMin(entity, LastActivityDateField),
                GetDateOrMin(entity, LastPasswordChangeDateField),
                GetDateOrMin(entity, LastLockoutDateField));

        }

        private static void SplitName(string Name, out string FirstName, out string LastName)
        {
            FirstName = LastName = "";
            if (Name.Contains(' '))
            {
                FirstName = Name.Substring(0, Name.IndexOf(' '));
                LastName = Name.Substring(Name.IndexOf(' ') + 1);
            }
            else
            {
                FirstName = Name;
            }
        }


        #endregion

        #region Properties

        private string _EntityName = "appl_webmembership";

        public string EntityName
        {
            get { return Settings.Parameters["entityName"]; }
        }

        private string _UserNameField = "appl_name";

        public string UserNameField
        {
            get { return Settings.Parameters["userNameField"]; }
        }

        private string _EmailField = "emailaddress";

        public string EmailField
        {
            get { return Settings.Parameters["emailField"]; }
        }

        private string _PasswordField = "appl_password";

        public string PasswordField
        {
            get { return Settings.Parameters["passwordField"]; }
        }

        private string _PasswordQuestionField = "appl_passwordquestion";

        public string PasswordQuestionField
        {
            get { return Settings.Parameters["passwordQuestionField"]; }
        }

        private string _PasswordAnswerField = "appl_passwordanswer";

        public string PasswordAnswerField
        {
            get { return Settings.Parameters["passwordAnswerField"]; }
        }

        private string _LastLoginDateField = "appl_lastlogindate";

        public string LastLoginDateField
        {
            get { return Settings.Parameters["lastLoginDateField"]; }
        }

        private string _LastActivityDateField = "appl_lastactivitydate";

        public string LastActivityDateField
        {
            get { return Settings.Parameters["lastActivityDateField"]; }
        }

        private string _LastPasswordChangeDateField = "appl_lastpasswordchangedate";

        public string LastPasswordChangeDateField
        {
            get { return Settings.Parameters["lastPasswordChangeDateField"]; }
        }

        private string _LastLockoutDateField = "appl_lastlockoutdate";

        public string LastLockoutDateField
        {
            get { return Settings.Parameters["lastLockoutDateField"]; }
        }

        private string _IsLockedoutField = "appl_islockedout";

        public string IsLockedoutField
        {
            get { return Settings.Parameters["isLockedoutField"]; }
        }

        private string _IsApprovedField = "appl_isapproved";

        public string IsApprovedField
        {
            get { return Settings.Parameters["isApprovedField"]; }

        }

        private string _RelatedContactField = "appl_contactid";

        public string RelatedContactField
        {
            get { return Settings.Parameters["relatedContactField"]; }
        }

        public string ApplicationNameField
        {
            get { return Settings.Parameters["applicationNameField"]; }
        }



        #endregion


        #region MembershipProvider Implementation

        public override string ApplicationName
        {
            get
            {
                return Settings.Parameters["applicationName"];
            }
            set
            {
            }
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            Entity ent = RetrieveMemberBy(UserNameField, username);
            if (ent != null && ent.GetAttributeValue<string>(PasswordField).Equals(oldPassword))
            {
                ent.SetAttributeValue<string>(PasswordField, newPassword);
                ent.SetAttributeValue<DateTime>(LastActivityDateField, DateTime.Now);
                ent.SetAttributeValue<DateTime>(LastPasswordChangeDateField, DateTime.Now);
                CrmCommon.UpdateEntity(ent, Connection);
                return true;
            }
            return false;
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            Entity ent = RetrieveMemberBy(UserNameField, username);
            if (ent != null && ent.GetAttributeValue<string>(PasswordField).Equals(password))
            {
                ent.SetAttributeValue<string>(PasswordQuestionField, newPasswordQuestion);
                ent.SetAttributeValue<string>(PasswordAnswerField, newPasswordAnswer);
                ent.SetAttributeValue<DateTime>(LastActivityDateField, DateTime.Now);
                CrmCommon.UpdateEntity(ent, Connection);
                return true;
            }
            return false;
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            try
            {

                FilterExpression filter = new FilterExpression(LogicalOperator.And);
                Entity MemberEntity = null;
                filter.AddCondition(new ConditionExpression(UserNameField, ConditionOperator.Equal, username));
                EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
                Guid Id = Guid.Empty;
                if (col.Entities.Count > 0)
                {
                    status = MembershipCreateStatus.DuplicateUserName;
                    return null;
                }
                MemberEntity = new Entity(EntityName);
                MemberEntity.SetAttributeValue<string>(UserNameField, username);
                MemberEntity.SetAttributeValue<string>(EmailField, email);
                MemberEntity.SetAttributeValue<string>(PasswordField, password);
                MemberEntity.SetAttributeValue<string>(PasswordQuestionField, passwordQuestion);
                MemberEntity.SetAttributeValue<string>(PasswordAnswerField, passwordAnswer);
                MemberEntity.SetAttributeValue<DateTime>(LastLoginDateField, DateTime.Now);
                MemberEntity.SetAttributeValue<DateTime>(LastActivityDateField, DateTime.Now);
                MemberEntity.SetAttributeValue<bool>(IsApprovedField, isApproved);
                MemberEntity.SetAttributeValue<bool>(IsLockedoutField, false);
                MemberEntity.SetAttributeValue<string>(ApplicationNameField, ApplicationName);

                // Find a related contact
                if (!string.IsNullOrEmpty(RelatedContactField))
                {
                    filter = new FilterExpression(LogicalOperator.And);
                    filter.AddCondition(new ConditionExpression("emailaddress1", ConditionOperator.Equal, email));
                    col = CrmCommon.RetrieveByFilter("contact", filter, Connection);
                    if (col.Entities.Count > 0)
                    {
                        MemberEntity.SetAttributeValue<EntityReference>(RelatedContactField, col.Entities.First().ToEntityReference());
                    }
                }

                Id = CrmCommon.CreateEntity(MemberEntity, Connection);

                // Make the Membership user
                MembershipUser user = new MembershipUser("CrmMembershipProvider",
                    username,
                    Id,
                    email,
                    passwordQuestion,
                    "",
                    isApproved,
                    MemberEntity.GetAttributeValue<bool>(IsLockedoutField),
                    MemberEntity.GetAttributeValue<DateTime>("createdon"),
                    MemberEntity.GetAttributeValue<DateTime>(LastLoginDateField),
                    MemberEntity.GetAttributeValue<DateTime>(LastActivityDateField),
                    MemberEntity.GetAttributeValue<DateTime>(LastPasswordChangeDateField),
                    DateTime.MinValue);

                status = MembershipCreateStatus.Success;
                return user;
            }
            catch (Exception ex)
            {
                status = MembershipCreateStatus.ProviderError;
                return null;
            }
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            try
            {
                Entity ent = RetrieveMemberBy(UserNameField, username);
                if (ent != null)
                {
                    CrmCommon.DeleteEntity(ent, Connection);
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                return false;
            }

        }

        public override bool EnablePasswordReset
        {
            get { return Convert.ToBoolean(Settings.Parameters["enablePasswordReset"]); }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return Convert.ToBoolean(Settings.Parameters["enablePasswordRetrieval"]); }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(EmailField, ConditionOperator.Equal, emailToMatch));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            totalRecords = col.TotalRecordCount;
            return ConvertToMembershipUserCollection(col);
        }



        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(UserNameField, ConditionOperator.Equal, usernameToMatch));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            totalRecords = col.TotalRecordCount;
            return ConvertToMembershipUserCollection(col);
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            totalRecords = col.TotalRecordCount;
            return ConvertToMembershipUserCollection(col);
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            Entity ent = RetrieveMemberBy(UserNameField, username);
            if (ent != null)
            {
                if (ent.GetAttributeValue<string>(PasswordAnswerField).Equals(answer, StringComparison.InvariantCultureIgnoreCase))
                {
                    return ent.GetAttributeValue<string>(PasswordField);
                }
            }
            return string.Empty;
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            Entity ent = RetrieveMemberBy(UserNameField, username);
            if (ent != null)
            {
                return ConvertToMembershipUser(ent);
            }
            else
            {
                return null;
            }
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            using (CrmOrganizationServiceContext service = new CrmOrganizationServiceContext(Connection))
            {
                Entity ent = service.Retrieve(EntityName, Guid.Parse(Convert.ToString(providerUserKey)), new ColumnSet(true));
                if (ent != null)
                {
                    return ConvertToMembershipUser(ent);
                }
            }
            return null;
        }

        public override string GetUserNameByEmail(string email)
        {
            Entity ent = RetrieveMemberBy(EmailField, email);
            if (ent != null)
            {
                return ent.GetAttributeValue<string>(UserNameField);
            }
            else
            {
                return null;
            }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return Convert.ToInt32(Settings.Parameters["maxInvalidPasswordAttempts"]); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return Convert.ToInt32(Settings.Parameters["minRequiredNonAlphanumericCharacters"]); }
        }

        public override int MinRequiredPasswordLength
        {
            get { return Convert.ToInt32(Settings.Parameters["minRequiredPasswordLength"]); }
        }

        public override int PasswordAttemptWindow
        {
            get { return Convert.ToInt32(Settings.Parameters["passwordAttemptWindow"]); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return MembershipPasswordFormat.Clear; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return string.Empty; }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return Convert.ToBoolean(Settings.Parameters["requiresQuestionAndAnswer"]); }
        }

        public override bool RequiresUniqueEmail
        {
            get { return Convert.ToBoolean(Settings.Parameters["requiresUniqueEmail"]); }
        }

        public override string ResetPassword(string username, string answer)
        {
            Entity ent = RetrieveMemberBy(UserNameField, username);
            if (ent != null && ent.GetAttributeValue<string>(PasswordAnswerField).Equals(answer, StringComparison.InvariantCultureIgnoreCase))
            {
                string NewPass = Guid.NewGuid().ToString().Replace("-", "").Replace("{", "").Replace("}", "").Substring(0, MinRequiredPasswordLength);
                ent.SetAttributeValue<string>(PasswordField, NewPass);
                ent.SetAttributeValue<DateTime>(LastActivityDateField, DateTime.Now);
                ent.SetAttributeValue<DateTime>(LastPasswordChangeDateField, DateTime.Now);
                CrmCommon.UpdateEntity(ent, Connection);
                return NewPass;
            }
            return string.Empty;
        }

        public override bool UnlockUser(string userName)
        {
            Entity ent = RetrieveMemberBy(UserNameField, userName);
            if (ent != null)
            {
                ent.SetAttributeValue<bool>(IsLockedoutField, false);
                ent.SetAttributeValue<DateTime>(LastActivityDateField, DateTime.Now);
                CrmCommon.UpdateEntity(ent, Connection);
                return true;
            }
            return false;
        }

        public override void UpdateUser(MembershipUser user)
        {
            using (CrmOrganizationServiceContext service = new CrmOrganizationServiceContext(Connection))
            {
                Entity entity = service.Retrieve(EntityName, Guid.Parse(Convert.ToString(user.ProviderUserKey)), new ColumnSet(true));
                if (entity != null)
                {
                    entity.SetAttributeValue<string>(UserNameField, user.UserName);
                    entity.SetAttributeValue<string>(EmailField, user.Email);
                    entity.SetAttributeValue<string>(PasswordQuestionField, user.PasswordQuestion);
                    entity.SetAttributeValue<DateTime>(LastActivityDateField, DateTime.Now);
                    CrmCommon.UpdateEntity(entity, Connection);
                }
            }
        }

        public override bool ValidateUser(string username, string password)
        {
            Entity ent = RetrieveMemberBy(UserNameField, username);
            if (ent != null && ent.GetAttributeValue<string>(PasswordField).Equals(password, StringComparison.InvariantCulture))
            {
                DateTime dt = DateTime.Now;
                ent.SetAttributeValue<DateTime>(LastLoginDateField, dt);
                ent.SetAttributeValue<DateTime>(LastActivityDateField, dt);
                CrmCommon.UpdateEntity(ent, Connection);
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion
    }
}
